<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8'>

    <link rel="stylesheet" type="text/css" 
      href="/assets/css/straybirds.css" media="screen" />
    <link rel="stylesheet" type="text/css" 
      href="/assets/css/pygments.css" media="screen" />

    <!-- MathJax Section Start -->

    <script type="text/javascript"
    src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
    </script>
    <script>
        MathJax.Hub.Config({
              tex2jax: {
              skipTags: ['script', 'noscript', 'style', 'textarea', 'pre']
              }
        });
        MathJax.Hub.Queue(function() {
            var all = MathJax.Hub.getAllJax(), i;
            for(i=0; i < all.length; i += 1) {
                all[i].SourceElement().parentNode.className += ' has-jax';
            }
        });
    </script>

    <!-- MathJax Section End -->

    <!-- Google Analytics Start-->
    <script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-48100787-1', 'minixalpha.github.io');
  ga('send', 'pageview');

</script>

    <!-- Google Analytics End -->

    <title>对C语言中的static关键字的深入理解</title>
  </head>

  <body>
    <div class="container">
      <header>
        <div class="container">
          <h1>
              <a href="/" title="Home Page"> 潇湘夜雨 </a>
          <span class="github-src">
            <a href ="https://github.com/minixalpha/minixalpha.github.io"
               target="_blank"
               title="Fork me on GitHub">
              <img src="/assets/images/GitHub-Mark-Light-32px.png" alt="">
            </a>
          </span>
          </h1>
        </div>
      </header>

      <aside id="left-side">
        <h2> 分类 </h2>
  <ul class="category-list">
      
            
                <li>
                <a href="/categories/计算机系统"> 计算机系统 (3) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/java"> java (1) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/技术"> 技术 (5) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/工具"> 工具 (4) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/科研"> 科研 (5) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/生活"> 生活 (1) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/思想"> 思想 (2) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/c语言"> c语言 (4) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/虚拟机"> 虚拟机 (1) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/英语"> 英语 (8) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/读书"> 读书 (1) </a>
                </li>
            
      
            
                <li>
                <a href="/categories/源代码阅读"> 源代码阅读 (10) </a>
                </li>
            
      
  </ul>

      </aside>

      <aside id="right-side">
        <h2> 归档 </h2>
  <ul class="archive-list">
    
    
    
        
        
        
        
            
            <li>
                <a href="/2014/05">
                    2014-05 (1)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2014/04">
                    2014-04 (3)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2014/03">
                    2014-03 (11)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2014/02">
                    2014-02 (6)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2014/01">
                    2014-01 (3)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2013/11">
                    2013-11 (10)
                </a>
            </li>

        
        
    
        
        
        
        
    
        
        
        
        
    
        
        
        
        
            
            <li>
                <a href="/2013/10">
                    2013-10 (3)
                </a>
            </li>

        
        
    
  </ul>

      </aside>

      <article>

<h1>对C语言中的static关键字的深入理解</h1>

<p>在阅读一些项目源代码时，我发现很多时候，会把函数和变量声明为static，所以，很好奇为什么要这样做，于是有了下面这篇文章。</p>

<h1>基本概念</h1>

<p>使用static有三种情况：</p>

<ul>
<li>函数内部static变量</li>
<li>函数外部static变量</li>
<li>static函数</li>
</ul>

<p>函数内部的static变量，关键在于生命周期持久，他的值不会随着函数调用的结束而消失，下一次调用时，static变量的值，还保留着上次调用后的内容。</p>

<p>函数外部的static变量，以及static函数，关键在于私有性，它们只属于当前文件，其它文件看不到他们。例如：</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="cm">/* test_static1.c */</span>
<span class="cp">#include &lt;stdio.h&gt;</span>

<span class="kt">void</span> <span class="nf">foo</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">bar</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="k">static</span> <span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span>
    <span class="n">printf</span> <span class="p">(</span><span class="s">&quot;%d </span><span class="se">\n</span><span class="s">&quot;</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
    <span class="n">printf</span> <span class="p">(</span><span class="s">&quot;%d </span><span class="se">\n</span><span class="s">&quot;</span><span class="p">,</span> <span class="n">j</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="cm">/* test_static2.c */</span>
<span class="kt">void</span> <span class="nf">foo</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">bar</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">static</span> <span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</code></pre></div>
<p>将两个文件一起编译</p>

<blockquote>
<p>gcc test<em>static1.c test</em>static2.c -o test_static</p>
</blockquote>

<p>编译器会提示：</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">/tmp/ccuerF9V.o: In function `foo&#39;:
test_static2.c:(.text+0x0): multiple definition of `foo&#39;
/tmp/cc9qncdw.o:test_static1.c:(.text+0x0): first defined here
/tmp/ccuerF9V.o:(.data+0x0): multiple definition of `i&#39;
/tmp/cc9qncdw.o:(.data+0x0): first defined here
collect2: ld returned 1 exit status
</code></pre></div>
<p>把与非static变量i相的语句注释掉就不会有此提示i重复定义了，原因就在于使用static声明后，变量私有化了，不同文件中的同名变量不会相互冲突。</p>

<p>static 函数也与此类似，将函数声明为static，说明我们只在当前文件中使用这个函数，其它文件看不到，即使重名，也不会相互冲突。</p>

<h1>深入理解</h1>

<blockquote>
<p>从来就不应该仅仅满足于了解现象，还要了解现象的背后有什么</p>
</blockquote>

<h2>为什么函数内部的static变量和普通函数变量生命周期不一样</h2>

<p>我们的程序，从源代码经过编译，链接生成了可执行文件，可执行文件被加载到存储器中，然后执行。以Unix程序为例，每个Unix程序都有一个运行时存储器映像。可以理解为程序运行时，存储器中的数据分布。</p>

<p><img src="/assets/blog-images/linux_run_time_memory_image.png" alt="linux_rtmi"></p>

<p>图1 Linux运行时存储器映像</p>

<p>当程序运行时，操作系统会创建用户栈(User stack)，一个函数被调用时，它的参数，局部变量，返回地址等等，都会被压入栈中，当函数执行结束后，这些数据就会被其它函数使用，所以函数调用结束后，局部变量的值不会被保持。我们将此区域放大，可以看到用户栈中都有哪些内容。</p>

<p><img src="/assets/blog-images/stack_frame_structure.png" alt="sfs"></p>

<p>图2 栈帧结构</p>

<p>而static变量与普通局部变量不同，它不是保留在栈中。注意图一中，有一块区域，&quot;Loaded from executable file&quot;，其中有一块 .data, .bss区，static变量会被存储在这里，所以函数调用结束后，static变量的值仍然会得到保留。而 .data, .bss区，executable file，与程序的编译，链接，相关。</p>

<p>首先，多个源代码会分别被编译成可重定位目标程序，然后链接器会最终生成可执行目标程序。可重定位目标程序的结构如图3所示，可以看出，此时，.data, .bss区，已经出现。</p>

<p><img src="/assets/blog-images/relocatable_elf.png" alt="re"></p>

<p>图3 可重定位目标程序</p>

<p>.data 区存储已经初始化的全局C变量，.bss 区存储没有初始化的全局C变量，而编译器会为每个static变量在.data或者.bss中分配空间。</p>

<p>可执行目标程序的结构如图4所示</p>

<p><img src="/assets/blog-images/executable_elf.png" alt="ee"></p>

<p>图4 可执行目标程序</p>

<p>将图4与图1比较，就会发现，可执行目标程序的一部分被加载到存储器中，这就是&quot;Loaded from executable file&quot;的来源。</p>

<p>另外，从图一中，也可以看出，使用malloc分配的内存空间，与函数局部变量，static变量的不同。</p>

<h2>为什么函数外部的static变量及static函数只对文件内部可见</h2>

<p>要解释这个问题，我们首先要理解问题本身。这个问题的本质其实是，当我们遇到一个变量或者函数时，我们去哪里寻找它，static变量/函数与普通变量/函数的寻找方式有什么不同。</p>

<p>我们回到刚才的例子，这一次，仔细地观察编译链接时的提示信息：</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="cm">/* test_static1.c */</span>
<span class="cp">#include &lt;stdio.h&gt;</span>

<span class="kt">void</span> <span class="nf">foo</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">bar</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="k">static</span> <span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span>
    <span class="n">printf</span> <span class="p">(</span><span class="s">&quot;%d </span><span class="se">\n</span><span class="s">&quot;</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
    <span class="n">printf</span> <span class="p">(</span><span class="s">&quot;%d </span><span class="se">\n</span><span class="s">&quot;</span><span class="p">,</span> <span class="n">j</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="cm">/* test_static2.c */</span>
<span class="kt">void</span> <span class="nf">foo</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">bar</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">static</span> <span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</code></pre></div>
<p>将两个文件一起编译</p>

<blockquote>
<p>gcc test<em>static1.c test</em>static2.c -o test_static</p>
</blockquote>

<p>编译器会提示：</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">/tmp/ccuerF9V.o: In function `foo&#39;:
test_static2.c:(.text+0x0): multiple definition of `foo&#39;
/tmp/cc9qncdw.o:test_static1.c:(.text+0x0): first defined here
/tmp/ccuerF9V.o:(.data+0x0): multiple definition of `i&#39;
/tmp/cc9qncdw.o:(.data+0x0): first defined here
collect2: ld returned 1 exit status
</code></pre></div>
<p>你会发现，虽然我们只用了一条命令对两个文件进行编译链接，但是，实际上，两个源文件是被分别编译成/tmp/ccuerF9V.o及/tmp/cc9qncdw.o，并且，错误并不是出现在编译时，而是出现在链接时，链接器ld返回了1。链接是把两个可重新定位的目标程序，组合在一起，组合的时候，我们发现了变量i及函数foo的定义出现冲突。而声明为static的变量j及函数bar并没有提示冲突。</p>

<p>这说明，在ld进行链接时，需要进行某种检查，去发现冲突。ld的输入是每个源文件生成的可重定位目标文件，那么这些目标文件里一定会有一些信息，告诉ld它们有什么变量，然后ld才能检查是不是有冲突。</p>

<p>说起<code>可重定位目标文件</code>，我们一直都没有解释为什么要重定位。其实这很好理解，一个源文件编译后，如果生成的目标文件中，各个地址就是最终运行时的地址，那么这些地址很可能会和其它文件中的地址冲突。因为编译一个文件时，我们不会知道有其它文件的存在，所以编译时无法确定最终的地址。因此，编译单个文件时，生成的目标文件中的地址都是从0开始，链接时，链接器会将不同目标文件中的地址重新定位，最终生成可执行文件。注意这里的冲突和前面说的冲突不是一回事，这里的冲突是不同的可重定位目标文件中相同地址的冲突，前面一段讲的是同名变量之间的冲突。</p>

<p>此时，我们不得不回到可重定位目标文件的格式。</p>

<p><img src="/assets/blog-images/relocatable_elf.png" alt="re"></p>

<p>图3 可重定位目标程序</p>

<p>注意 .symtab节，这个节存储符号表，假设当前可重定位目标模块为m, 符号表会告诉我们m中定义和引用的符号信息，主要分为：</p>

<ul>
<li>m定义，并可以被其它模块引用的全局符号：m中的非static函数，非static全局变量。</li>
<li>由其它模块定义，并被m引用的全局符号：m中使用extern声明的变量</li>
<li>只被m引用的本地符号：m中的static函数，static全局变量。</li>
</ul>

<p>现在编译一下，然后用GNU READELF工具看一下符号表。</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">    $ gcc -c test_static1.c -o test_static1.o
    $ readelf -s test_static1.o

Symbol table &#39;.symtab&#39; contains 15 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS test_static1.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 SECTION LOCAL  DEFAULT    4 
     5: 00000005     5 FUNC    LOCAL  DEFAULT    1 bar
     6: 00000004     4 OBJECT  LOCAL  DEFAULT    3 j
     7: 00000000     0 SECTION LOCAL  DEFAULT    5 
     8: 00000000     0 SECTION LOCAL  DEFAULT    7 
     9: 00000000     0 SECTION LOCAL  DEFAULT    8 
    10: 00000000     0 SECTION LOCAL  DEFAULT    6 
    11: 00000000     5 FUNC    GLOBAL DEFAULT    1 foo
    12: 00000000     4 OBJECT  GLOBAL DEFAULT    3 i
    13: 0000000a    62 FUNC    GLOBAL DEFAULT    1 main
    14: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
</code></pre></div>
<p>表的数据结构不解释，有兴趣，看扩展阅读部分。</p>

<p>现在，假如你是链接器ld，我给你2个可重定位目标程序，你从中得到两个符号表，这时候，你就可以检查出两个符号表是否存在冲突了。</p>

<p>由于全局符号可能会定义相同的名字，链接器会有一套规则，来确定选择哪个符号。符号分为强符号与弱符号。</p>

<ul>
<li>强符号：函数和已经初始化的全局变量是强符号</li>
<li>弱符号：未初始化的全局变量是弱符号</li>
</ul>

<p>处理相同名字的全局符号的规则是：</p>

<ol>
<li>不允许有多个强符号</li>
<li>如果有一个强符号，多个弱符号，那么选择强符号</li>
<li>如果有多个弱符号，那么从中任意选择一个</li>
</ol>

<p>明白了这些规则，你其实可以明白很多事情，不仅仅包括什么时候，变量名，函数名会冲突，还包括为什么要尽量避免使用全局变量，为什么要使用static把数据私有化。看看规则3，“任意”两个字，有没有让你感觉有一丝不适。</p>

<p>这也是为什么我们要探索事物背后机理的原因，不仅仅是在出现错误时，我们知道哪里有问题，还帮助我们写出更健壮的程序。</p>

<h1>扩展阅读</h1>

<p>《深入理解计算机系统》(Computer Systems, A Programmer&#39;s Perspective): 第七章 链接</p>


      </article>

      <div class="comments">
        
          <div id="disqus_thread"></div>
 <script type="text/javascript">
     /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
     var disqus_shortname = 'minixalpha'; // required: replace example with your forum shortname

     /* * * DON'T EDIT BELOW THIS LINE * * */
     (function() {
         var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
         dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
         (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
     })();
 </script>
 <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
    <a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
    

        
      </div>


      <footer>
        Copyright (c) minixalpha 2014
      </footer>

    </div>
  </body>
</html>
